package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"data-treating/pkg/types"

	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
)

// 服务器响应结构体
type TaskListResponse struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Data    struct {
		Items []TaskItem `json:"items"`
		Limit int        `json:"limit"`
		Page  int        `json:"page"`
		Total int        `json:"total"`
	} `json:"data"`
}

type TaskItem struct {
	ID           string `json:"id"`
	TaskName     string `json:"task_name"`
	TaskType     string `json:"task_type"`
	Status       int    `json:"status"`
	UserID       string `json:"user_id"`
	UserName     string `json:"user_name"`
	UserNumber   string `json:"user_number"`
	DeviceBindID string `json:"device_bind_id"`
	BoxName      string `json:"box_name"`
	SiteID       string `json:"site_id"`
	SiteName     string `json:"site_name"`
	Content      string `json:"content"`
	Score        string `json:"score"`
	CreateTime   string `json:"create_time"`
	StartTime    string `json:"start_time"`
	FinishTime   string `json:"finish_time"`
	IsDeleted    bool   `json:"is_deleted"`
}

type ProcessDataResponse struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Data    struct {
		Total int           `json:"total"`
		Items []ProcessItem `json:"items"`
		Page  int           `json:"page"`
		Pages int           `json:"pages"`
	} `json:"data"`
}

type ProcessItem struct {
	TaskID     string `json:"task_id"`
	Timestamp  int64  `json:"timestamp"`
	TypePoints int    `json:"type_points"`
	Points     string `json:"points"`
	ScoreType  int    `json:"score_type"`
	Score      string `json:"score"`
	ImageID    string `json:"image_id"`
}

// 本地保存的数据结构
type LocalPoseData struct {
	PoseData types.PoseData `json:"pose_data"`
}

// 配置结构体
type Config struct {
	ServerURL  string
	StartTime  string
	EndTime    string
	OutputDir  string
	PageSize   int
	MaxRetries int
	Timeout    time.Duration
}

// 全局变量
var (
	config Config
	logger *logrus.Logger
)

func main() {
	// 初始化日志
	logger = logrus.New()
	logger.SetFormatter(&logrus.TextFormatter{
		FullTimestamp: true,
	})

	// 创建命令行应用
	var rootCmd = &cobra.Command{
		Use:   "pulldata",
		Short: "从服务器下载任务数据并转换为标准JSON格式",
		Long: `从指定服务器下载任务数据，转换关键点数据格式，并保存到本地文件。
支持按时间范围过滤任务，分页下载等功能。`,
		RunE: run,
	}

	// 添加命令行参数
	rootCmd.Flags().StringVarP(&config.ServerURL, "server", "s", "http://xxchuan.com:3036", "服务器访问URL")
	rootCmd.Flags().StringVarP(&config.StartTime, "start", "a", "", "开始时间 (格式: 2025-01-01)")
	rootCmd.Flags().StringVarP(&config.EndTime, "end", "b", "", "结束时间 (格式: 2025-01-01)")
	rootCmd.Flags().StringVarP(&config.OutputDir, "output", "o", "posedata", "输出目录")
	rootCmd.Flags().IntVarP(&config.PageSize, "page-size", "p", 20, "分页大小")
	rootCmd.Flags().IntVarP(&config.MaxRetries, "retries", "r", 3, "最大重试次数")
	rootCmd.Flags().DurationVarP(&config.Timeout, "timeout", "t", 30*time.Second, "请求超时时间")

	// 执行命令
	if err := rootCmd.Execute(); err != nil {
		logger.Fatal(err)
	}
}

func run(cmd *cobra.Command, args []string) error {
	logger.Info("开始下载任务数据...")
	logger.Infof("服务器URL: %s", config.ServerURL)
	logger.Infof("时间范围: %s 到 %s", config.StartTime, config.EndTime)
	logger.Infof("输出目录: %s", config.OutputDir)

	// 创建输出目录
	if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
		return fmt.Errorf("创建输出目录失败: %v", err)
	}

	// 获取任务列表
	tasks, err := getTaskList()
	if err != nil {
		return fmt.Errorf("获取任务列表失败: %v", err)
	}

	logger.Infof("找到 %d 个任务", len(tasks))

	// 下载每个任务的数据
	successCount := 0
	skipCount := 0
	errorCount := 0

	for i, task := range tasks {
		logger.Infof("处理任务 %d/%d: %s (用户: %s)", i+1, len(tasks), task.TaskName, task.UserName)

		// 检查时间范围
		if !isInTimeRange(task, config.StartTime, config.EndTime) {
			logger.Infof("跳过任务 %s: 不在指定时间范围内", task.TaskName)
			skipCount++
			continue
		}

		// 下载任务数据
		if err := downloadTaskData(task); err != nil {
			logger.Errorf("下载任务 %s 数据失败: %v", task.TaskName, err)
			errorCount++
			continue
		}

		successCount++
	}

	logger.Info("下载完成!")
	logger.Infof("成功: %d, 跳过: %d, 失败: %d", successCount, skipCount, errorCount)

	return nil
}

// 获取任务列表
func getTaskList() ([]TaskItem, error) {
	var allTasks []TaskItem
	page := 1

	for {
		logger.Infof("获取第 %d 页任务列表...", page)

		// 构建请求URL
		apiURL := fmt.Sprintf("%s/api/v1/task/person/get/all?page=%d&limit=%d",
			config.ServerURL, page, config.PageSize)

		// 发送请求
		resp, err := makeHTTPRequest(apiURL)
		if err != nil {
			return nil, err
		}

		// 解析响应
		var taskResp TaskListResponse
		if err := json.Unmarshal(resp, &taskResp); err != nil {
			return nil, fmt.Errorf("解析任务列表响应失败: %v", err)
		}

		if taskResp.Code != 200 {
			return nil, fmt.Errorf("服务器返回错误: %s", taskResp.Message)
		}

		// 添加任务到列表
		allTasks = append(allTasks, taskResp.Data.Items...)

		// 检查是否还有更多页
		if len(taskResp.Data.Items) < config.PageSize {
			break
		}

		page++
	}

	return allTasks, nil
}

// 下载任务数据
func downloadTaskData(task TaskItem) error {
	// 生成文件名
	fileName := generateFileName(task)
	filePath := filepath.Join(config.OutputDir, fileName)

	// 检查文件是否已存在
	if _, err := os.Stat(filePath); err == nil {
		logger.Infof("文件已存在，跳过: %s", fileName)
		return nil
	}

	// 获取任务数据
	processData, err := getProcessData(task.ID)
	if err != nil {
		return err
	}

	if len(processData) == 0 {
		logger.Warnf("任务 %s 没有数据，跳过", task.TaskName)
		return nil
	}

	// 转换数据格式
	poseDataList, err := convertProcessData(processData)
	if err != nil {
		logger.Errorf("转换任务 %s 数据失败: %v", task.TaskName, err)
		return err
	}

	// 检查转换后的数据是否为空
	if len(poseDataList) == 0 {
		logger.Warnf("任务 %s 转换后没有有效数据，跳过", task.TaskName)
		return nil
	}

	// 检查数据质量
	if !isDataValid(poseDataList) {
		logger.Warnf("任务 %s 数据质量不合格，跳过", task.TaskName)
		return nil
	}

	// 保存到文件
	if err := saveToFile(filePath, poseDataList); err != nil {
		// 如果保存失败，尝试删除已创建的文件
		if _, statErr := os.Stat(filePath); statErr == nil {
			if removeErr := os.Remove(filePath); removeErr != nil {
				logger.Errorf("删除失败文件 %s 时出错: %v", fileName, removeErr)
			} else {
				logger.Infof("已删除失败文件: %s", fileName)
			}
		}
		return err
	}

	logger.Infof("成功保存任务数据: %s (%d 帧)", fileName, len(poseDataList))
	return nil
}

// 获取处理数据
func getProcessData(taskID string) ([]ProcessItem, error) {
	var allData []ProcessItem
	page := 1

	for {
		logger.Debugf("获取任务 %s 第 %d 页数据...", taskID, page)

		// 构建请求URL
		apiURL := fmt.Sprintf("%s/api/v1/process/get/%s?page=%d&limit=1000",
			config.ServerURL, taskID, page)

		// 发送请求
		resp, err := makeHTTPRequest(apiURL)
		if err != nil {
			return nil, err
		}

		// 解析响应
		var processResp ProcessDataResponse
		if err := json.Unmarshal(resp, &processResp); err != nil {
			return nil, fmt.Errorf("解析处理数据响应失败: %v", err)
		}

		if processResp.Code != 200 {
			return nil, fmt.Errorf("服务器返回错误: %s", processResp.Message)
		}

		// 添加数据到列表
		allData = append(allData, processResp.Data.Items...)

		// 检查是否还有更多页
		if page >= processResp.Data.Pages {
			break
		}

		page++
	}

	return allData, nil
}

// 检查数据质量
func isDataValid(poseDataList []LocalPoseData) bool {
	if len(poseDataList) == 0 {
		return false
	}

	validFrameCount := 0
	totalFrames := len(poseDataList)

	for i, poseData := range poseDataList {
		if isValidPoseData(poseData.PoseData) {
			validFrameCount++
		} else {
			logger.Debugf("第 %d 帧数据无效", i+1)
		}
	}

	// 计算有效帧比例
	validRatio := float64(validFrameCount) / float64(totalFrames)

	logger.Debugf("数据质量检查: 总帧数=%d, 有效帧数=%d, 有效比例=%.2f",
		totalFrames, validFrameCount, validRatio)

	// 如果有效帧比例低于50%，认为数据质量不合格
	if validRatio < 0.5 {
		logger.Warnf("数据质量不合格: 有效帧比例 %.2f < 0.5", validRatio)
		return false
	}

	// 如果有效帧数少于5帧，认为数据质量不合格
	if validFrameCount < 5 {
		logger.Warnf("数据质量不合格: 有效帧数 %d < 5", validFrameCount)
		return false
	}

	return true
}

// 检查单个姿势数据是否有效
func isValidPoseData(poseData types.PoseData) bool {
	// 检查关键点数组是否为空
	if len(poseData.Keypoints) == 0 {
		return false
	}

	// 检查关键点数量是否正确
	if len(poseData.Keypoints) != 17 {
		return false
	}

	validKeypointCount := 0
	hasValidCoordinates := false

	for i, keypoint := range poseData.Keypoints {
		// 检查坐标是否有效（不为0或负数）
		if keypoint.X > 0 && keypoint.Y > 0 {
			hasValidCoordinates = true
			validKeypointCount++
		}

		// 检查置信度是否在合理范围内
		if keypoint.Confidence < 0 || keypoint.Confidence > 1 {
			logger.Debugf("关键点 %d 置信度异常: %.2f", i, keypoint.Confidence)
		}
	}

	// 至少需要50%的关键点有效
	if float64(validKeypointCount)/17.0 < 0.5 {
		return false
	}

	// 必须至少有一个有效的坐标
	if !hasValidCoordinates {
		return false
	}

	return true
}

// 转换处理数据格式
func convertProcessData(processData []ProcessItem) ([]LocalPoseData, error) {
	var poseDataList []LocalPoseData
	var parseErrors []string

	for i, item := range processData {
		// 检查原始数据是否为空
		if strings.TrimSpace(item.Points) == "" {
			logger.Debugf("第 %d 帧关键点数据为空", i+1)
			continue
		}

		// 解析关键点数据
		keypoints, err := parsePoints(item.Points)
		if err != nil {
			errorMsg := fmt.Sprintf("第 %d 帧关键点数据解析失败: %v", i+1, err)
			logger.Warnf(errorMsg)
			parseErrors = append(parseErrors, errorMsg)
			continue
		}

		// 检查解析后的关键点是否有效
		if !isKeypointsValid(keypoints) {
			logger.Debugf("第 %d 帧关键点数据无效", i+1)
			continue
		}

		// 创建姿势数据
		poseData := types.PoseData{
			Keypoints: keypoints,
			Timestamp: item.Timestamp * 1000000, // 转换为纳秒
			FrameID:   i + 1,
		}

		poseDataList = append(poseDataList, LocalPoseData{
			PoseData: poseData,
		})
	}

	// 如果解析错误太多，返回错误
	if len(parseErrors) > 0 {
		logger.Warnf("发现 %d 个解析错误", len(parseErrors))
		if len(parseErrors) > len(processData)/2 {
			return poseDataList, fmt.Errorf("数据格式错误过多: %d/%d 帧解析失败", len(parseErrors), len(processData))
		}
	}

	return poseDataList, nil
}

// 检查关键点数组是否有效
func isKeypointsValid(keypoints [17]types.Point) bool {
	validCount := 0

	for i, keypoint := range keypoints {
		// 检查坐标是否在合理范围内
		if keypoint.X > 0 && keypoint.Y > 0 &&
			keypoint.X < 10000 && keypoint.Y < 10000 { // 假设图像尺寸不超过10000x10000
			validCount++
		}

		// 检查置信度是否在合理范围内
		if keypoint.Confidence < 0 || keypoint.Confidence > 1 {
			logger.Debugf("关键点 %d 置信度异常: %.2f", i, keypoint.Confidence)
		}
	}

	// 至少需要50%的关键点有效
	return float64(validCount)/17.0 >= 0.5
}

// 解析关键点字符串
func parsePoints(pointsStr string) ([17]types.Point, error) {
	var keypoints [17]types.Point

	// 检查输入字符串是否为空
	if strings.TrimSpace(pointsStr) == "" {
		return keypoints, fmt.Errorf("关键点字符串为空")
	}

	// 移除方括号
	pointsStr = strings.Trim(pointsStr, "[]")

	// 检查字符串是否为空（移除方括号后）
	if strings.TrimSpace(pointsStr) == "" {
		return keypoints, fmt.Errorf("关键点字符串格式错误: 移除方括号后为空")
	}

	// 分割点数据
	pointStrs := strings.Split(pointsStr, "},{")

	if len(pointStrs) != 17 {
		return keypoints, fmt.Errorf("关键点数量不正确: 期望17个，实际%d个", len(pointStrs))
	}

	for i, pointStr := range pointStrs {
		// 清理字符串
		pointStr = strings.Trim(pointStr, "{}")

		// 检查点字符串是否为空
		if strings.TrimSpace(pointStr) == "" {
			return keypoints, fmt.Errorf("第 %d 个关键点字符串为空", i+1)
		}

		// 解析x和y坐标
		parts := strings.Split(pointStr, ",")
		if len(parts) != 2 {
			return keypoints, fmt.Errorf("第 %d 个关键点格式错误: %s", i+1, pointStr)
		}

		// 解析x坐标
		xStr := strings.TrimSpace(strings.TrimPrefix(parts[0], "x="))
		if xStr == "" {
			return keypoints, fmt.Errorf("第 %d 个关键点x坐标为空", i+1)
		}
		x, err := strconv.ParseFloat(xStr, 64)
		if err != nil {
			return keypoints, fmt.Errorf("第 %d 个关键点x坐标解析失败: %v", i+1, err)
		}

		// 解析y坐标
		yStr := strings.TrimSpace(strings.TrimPrefix(parts[1], "y="))
		if yStr == "" {
			return keypoints, fmt.Errorf("第 %d 个关键点y坐标为空", i+1)
		}
		y, err := strconv.ParseFloat(yStr, 64)
		if err != nil {
			return keypoints, fmt.Errorf("第 %d 个关键点y坐标解析失败: %v", i+1, err)
		}

		// 检查坐标值是否合理
		if x < 0 || y < 0 {
			return keypoints, fmt.Errorf("第 %d 个关键点坐标无效: x=%f, y=%f", i+1, x, y)
		}

		// 设置关键点数据
		keypoints[i] = types.Point{
			X:          x,
			Y:          y,
			Confidence: 0.9, // 默认置信度
		}
	}

	return keypoints, nil
}

// 生成文件名
func generateFileName(task TaskItem) string {
	// 解析创建时间
	createTime, err := time.Parse("2006-01-02T15:04:05.999Z", task.CreateTime)
	if err != nil {
		createTime = time.Now()
	}

	// 格式化时间
	timeStr := createTime.Format("20060102_150405")

	// 生成文件名: 任务ID+用户名+任务时间+".json"
	fileName := fmt.Sprintf("%s_%s_%s.json", timeStr, task.UserName, task.ID)

	// 替换文件名中的非法字符
	fileName = strings.ReplaceAll(fileName, " ", "_")
	fileName = strings.ReplaceAll(fileName, ":", "_")

	return fileName
}

// 保存到文件
func saveToFile(filePath string, poseDataList []LocalPoseData) error {
	// 创建文件
	file, err := os.Create(filePath)
	if err != nil {
		return fmt.Errorf("创建文件失败: %v", err)
	}
	defer file.Close()

	// 写入JSON数据
	encoder := json.NewEncoder(file)
	encoder.SetIndent("", "  ")

	if err := encoder.Encode(poseDataList); err != nil {
		return fmt.Errorf("写入JSON数据失败: %v", err)
	}

	return nil
}

// 检查任务是否在时间范围内
func isInTimeRange(task TaskItem, startTime, endTime string) bool {
	if startTime == "" && endTime == "" {
		return true // 没有时间限制
	}

	// 解析任务创建时间
	taskTime, err := time.Parse("2006-01-02T15:04:05.999Z", task.CreateTime)
	if err != nil {
		logger.Warnf("解析任务时间失败: %v", err)
		return true // 解析失败时默认包含
	}

	// 检查开始时间
	if startTime != "" {
		start, err := time.Parse("2006-01-02", startTime)
		if err != nil {
			logger.Warnf("解析开始时间失败: %v", err)
		} else if taskTime.Before(start) {
			return false
		}
	}

	// 检查结束时间
	if endTime != "" {
		end, err := time.Parse("2006-01-02", endTime)
		if err != nil {
			logger.Warnf("解析结束时间失败: %v", err)
		} else {
			// 结束时间加一天，包含整个结束日期
			end = end.Add(24 * time.Hour)
			if taskTime.After(end) {
				return false
			}
		}
	}

	return true
}

// 发送HTTP请求
func makeHTTPRequest(url string) ([]byte, error) {
	client := &http.Client{
		Timeout: config.Timeout,
	}

	for attempt := 1; attempt <= config.MaxRetries; attempt++ {
		logger.Debugf("发送HTTP请求 (尝试 %d/%d): %s", attempt, config.MaxRetries, url)

		resp, err := client.Get(url)
		if err != nil {
			if attempt == config.MaxRetries {
				return nil, fmt.Errorf("HTTP请求失败: %v", err)
			}
			logger.Warnf("HTTP请求失败，重试中: %v", err)
			time.Sleep(time.Duration(attempt) * time.Second)
			continue
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			if attempt == config.MaxRetries {
				return nil, fmt.Errorf("HTTP状态码错误: %d", resp.StatusCode)
			}
			logger.Warnf("HTTP状态码错误 %d，重试中", resp.StatusCode)
			time.Sleep(time.Duration(attempt) * time.Second)
			continue
		}

		body, err := io.ReadAll(resp.Body)
		if err != nil {
			if attempt == config.MaxRetries {
				return nil, fmt.Errorf("读取响应体失败: %v", err)
			}
			logger.Warnf("读取响应体失败，重试中: %v", err)
			time.Sleep(time.Duration(attempt) * time.Second)
			continue
		}

		return body, nil
	}

	return nil, fmt.Errorf("达到最大重试次数")
}
